package airacrobats.game.object;

import airacrobats.io.data.JWavefrontModel;
import airacrobats.sound.Sound;
import airacrobats.sound.Sound3D;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.swing.JPanel;
import javax.vecmath.Matrix4f;
import javax.vecmath.Point3f;
import javax.vecmath.Quat4f;

/**
 * This class represents a game object.
 */
public abstract class GameObject
{
    /**
     * The unit size (in meters).
     */
    protected final static float UNITARY_SIZE = 10.0f;

    /**
     * Model.
     */
    protected JWavefrontModel model;
    /**
     * The shadow model.
     */
    private JWavefrontModel shadowModel;
    /**
     * Position.
     */
    protected Point3f position;
    /**
     * The rotation quaternion.
     */
    protected Quat4f rQuaternion;
    /**
     * The rotation matrix (calculated).
     */
    protected Matrix4f rMatrix;
    /**
     * The rotation matrix inverse (calculated).
     */
    protected Matrix4f rMatrixInverse;
    /**
     * Object sounds.
     */
    protected Sound3D[] sounds;

    /**
     * @param model The model.
     * @param shadowModel The shadow model.
     * @param position The initial position.
     * @param sounds Object sounds.
     */
    public GameObject(JWavefrontModel model, JWavefrontModel shadowModel, Point3f position, Sound3D[] sounds)
    {
        this.model = model;
        this.shadowModel = shadowModel;
        this.position = position;
        this.sounds = sounds;
        rQuaternion = new Quat4f(new float[]
                {
                    0.0f, 0.0f, 0.0f, 1.0f
                });
    }

    /**
     * Compile the models if they aren't already compiled.
     * @param gLAutoDrawable The auto drawable context.
     * @param mode The compile mode.
     */
    public void compileModels(GLAutoDrawable gLAutoDrawable, int mode)
    {
        model.compile(gLAutoDrawable, mode);
    }

    /**
     * Compile the shadow models if they aren't already compiled.
     * @param gLAutoDrawable The auto drawable context.
     * @param mode The compile mode.
     */
    public void compileShadowModels(GLAutoDrawable gLAutoDrawable, int mode)
    {
        if (shadowModel != null)
            shadowModel.compile(gLAutoDrawable, mode);
    }

    /**
     * @return The location.
     */
    public Point3f getPosition()
    {
        return position;
    }

    /**
     * @return The object speed.
     */
    public float getSpeed()
    {
        return 0.0f;
    }

    /**
     * Draws the object. The MODELVIEW matrix must be set.
     * @param drawable The drawable context.
     */
    public void draw(GLAutoDrawable drawable)
    {
        GL2 gl = drawable.getGL().getGL2();

        gl.glPushMatrix();

        doDraw(drawable, model);

        gl.glPopMatrix();

        gl.glFlush();
    }

    /**
     * Draws the shadow of the object.
     * @param drawable The drawable context.
     */
    public void drawShadow(GLAutoDrawable drawable)
    {
        if (rMatrix == null)
            return;
        
        GL2 gl = drawable.getGL().getGL2();

        gl.glPushMatrix();

        doDraw(drawable, shadowModel);

        gl.glPopMatrix();

        gl.glFlush();
    }

    /**
     * Updates the object.
     * @param deltaTime The delta time.
     * @param time The time elapsed.
     * @param cameraPosition Camera position.
     * @throws Exception Thrown if an error occurs.
     */
    public void update(long deltaTime, long time, Point3f cameraPosition) throws Exception
    {
        generateMatrixes();
        if (deltaTime > 0)
        {
            doUpdate(deltaTime, time, cameraPosition);
        }
    }

    /**
     * @return The inverse of the rotation matrix.
     */
    public Matrix4f getRMatrixInverse()
    {
        return rMatrixInverse;
    }

    /**
     * @return The sounds.
     */
    public Sound[] getSounds()
    {
        return sounds;
    }

    /**
     * Creates the info panel to be showed and adds it to the owner panel.
     * @param owner The owner panel.
     */
    public abstract void setupInfoPanel(JPanel owner);

    /**
     * Transforms the model view matrix (it must be set!). This is used to make changes on the camera view.
     * @param gl The gl object.
     */
    public abstract void transformModelview(GL2 gl);
    
    /**
     * Updates the object.
     * @param deltaTime The delta time.
     * @param time Time elapsed.
     * @param cameraPosition The camera position.
     * @throws Exception Thrown if an error occurs.
     */
    protected abstract void doUpdate(long deltaTime, long time, Point3f cameraPosition) throws Exception;

    /**
     * Draws the object.
     * @param drawable The drawable context.
     * @param model The model to use on the drawing.
     */
    protected abstract void doDraw(GLAutoDrawable drawable, JWavefrontModel model);

    /**
     * Calculates the rotation matrixes.
     */
    private void generateMatrixes()
    {
        rMatrix = generateMatrix();
        rMatrixInverse = new Matrix4f(rMatrix);
        rMatrixInverse.invert();
    }

    /**
     * Calculates the rotation matrix from the rotation quaternion.
     */
    private Matrix4f generateMatrix()
    {
        float[] matrix = new float[16];

        // First row.
        matrix[0] = 1.0f - 2.0f * (rQuaternion.y * rQuaternion.y + rQuaternion.z * rQuaternion.z);
        matrix[1] = 2.0f * (rQuaternion.x * rQuaternion.y + rQuaternion.z * rQuaternion.w);
        matrix[2] = 2.0f * (rQuaternion.x * rQuaternion.z - rQuaternion.y * rQuaternion.w);
        matrix[3] = 0.0f;

        // Second row.
        matrix[4] = 2.0f * (rQuaternion.x * rQuaternion.y - rQuaternion.z * rQuaternion.w);
        matrix[5] = 1.0f - 2.0f * (rQuaternion.x * rQuaternion.x + rQuaternion.z * rQuaternion.z);
        matrix[6] = 2.0f * (rQuaternion.z * rQuaternion.y + rQuaternion.x * rQuaternion.w);
        matrix[7] = 0.0f;

        // Third row.
        matrix[8] = 2.0f * (rQuaternion.x * rQuaternion.z + rQuaternion.y * rQuaternion.w);
        matrix[9] = 2.0f * (rQuaternion.y * rQuaternion.z - rQuaternion.x * rQuaternion.w);
        matrix[10] = 1.0f - 2.0f * (rQuaternion.x * rQuaternion.x + rQuaternion.y * rQuaternion.y);
        matrix[11] = 0.0f;

        // Fourth row.
        matrix[12] = 0;
        matrix[13] = 0;
        matrix[14] = 0;
        matrix[15] = 1.0f;

        return new Matrix4f(matrix);
    }
}
